/**
 * @file lv_event.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_obj.h"
#include "lv_indev.h"

/*********************
 *      DEFINES
 *********************/
#define MY_CLASS &lv_obj_class

/**********************
 *      TYPEDEFS
 **********************/
typedef struct _lv_event_dsc_t
{
    lv_event_cb_t cb;
    void* user_data;
    uint8_t filter;
} lv_event_dsc_t;

/**********************
 *  STATIC PROTOTYPES
 **********************/
static lv_event_dsc_t* lv_obj_get_event_dsc(const lv_obj_t* obj, uint32_t id);
static lv_res_t event_send_core(lv_event_t* e);
static bool event_is_bubbled(lv_event_t* e);

/**********************
 *  STATIC VARIABLES
 **********************/
static lv_event_t* event_head;

/**********************
 *      MACROS
 **********************/
#if LV_LOG_TRACE_EVENT
#define EVENT_TRACE(...) LV_LOG_TRACE(__VA_ARGS__)
#else
#define EVENT_TRACE(...)
#endif

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

lv_res_t lv_event_send(lv_obj_t* obj, lv_event_code_t event_code, void* param)
{
    if (obj == NULL)
    {
        return LV_RES_OK;
    }

    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_event_t e;
    e.target = obj;
    e.current_target = obj;
    e.code = event_code;
    e.user_data = NULL;
    e.param = param;
    e.deleted = 0;
    e.stop_bubbling = 0;
    e.stop_processing = 0;

    /*Build a simple linked list from the objects used in the events
     *It's important to know if this object was deleted by a nested event
     *called from this `event_cb`.*/
    e.prev = event_head;
    event_head = &e;

    /*Send the event*/
    lv_res_t res = event_send_core(&e);

    /*Remove this element from the list*/
    event_head = e.prev;

    return res;
}

lv_res_t lv_obj_event_base(const lv_obj_class_t* class_p, lv_event_t* e)
{
    const lv_obj_class_t* base;

    if (class_p == NULL)
    {
        base = e->current_target->class_p;
    }
    else
    {
        base = class_p->base_class;
    }

    /*Find a base in which call the ancestor's event handler_cb if set*/
    while (base && base->event_cb == NULL)
    {
        base = base->base_class;
    }

    if (base == NULL)
    {
        return LV_RES_OK;
    }

    if (base->event_cb == NULL)
    {
        return LV_RES_OK;
    }

    /*Call the actual event callback*/
    e->user_data = NULL;
    base->event_cb(base, e);

    lv_res_t res = LV_RES_OK;

    /*Stop if the object is deleted*/
    if (e->deleted)
    {
        res = LV_RES_INV;
    }

    return res;
}

lv_obj_t* lv_event_get_target(lv_event_t* e)
{
    return e->target;
}

lv_obj_t* lv_event_get_current_target(lv_event_t* e)
{
    return e->current_target;
}

lv_event_code_t lv_event_get_code(lv_event_t* e)
{
    return e->code & ~LV_EVENT_PREPROCESS;
}

void* lv_event_get_param(lv_event_t* e)
{
    return e->param;
}

void* lv_event_get_user_data(lv_event_t* e)
{
    return e->user_data;
}

void lv_event_stop_bubbling(lv_event_t* e)
{
    e->stop_bubbling = 1;
}

void lv_event_stop_processing(lv_event_t* e)
{
    e->stop_processing = 1;
}

uint32_t lv_event_register_id(void)
{
    static uint32_t last_id = _LV_EVENT_LAST;
    last_id ++;
    return last_id;
}

void _lv_event_mark_deleted(lv_obj_t* obj)
{
    lv_event_t* e = event_head;

    while (e)
    {
        if (e->current_target == obj || e->target == obj)
        {
            e->deleted = 1;
        }

        e = e->prev;
    }
}

struct _lv_event_dsc_t* lv_obj_add_event_cb(lv_obj_t* obj,
        lv_event_cb_t event_cb, lv_event_code_t filter,
        void* user_data)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    lv_obj_allocate_spec_attr(obj);

    obj->spec_attr->event_dsc_cnt++;
    obj->spec_attr->event_dsc = lv_mem_realloc(obj->spec_attr->event_dsc,
                                obj->spec_attr->event_dsc_cnt * sizeof(lv_event_dsc_t));
    LV_ASSERT_MALLOC(obj->spec_attr->event_dsc);

    obj->spec_attr->event_dsc[obj->spec_attr->event_dsc_cnt - 1].cb = event_cb;
    obj->spec_attr->event_dsc[obj->spec_attr->event_dsc_cnt - 1].filter = filter;
    obj->spec_attr->event_dsc[obj->spec_attr->event_dsc_cnt - 1].user_data =
        user_data;

    return &obj->spec_attr->event_dsc[obj->spec_attr->event_dsc_cnt - 1];
}

bool lv_obj_remove_event_cb(lv_obj_t* obj, lv_event_cb_t event_cb)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    if (obj->spec_attr == NULL)
    {
        return false;
    }

    int32_t i = 0;

    for (i = 0; i < obj->spec_attr->event_dsc_cnt; i++)
    {
        if (event_cb == NULL || obj->spec_attr->event_dsc[i].cb == event_cb)
        {
            /*Shift the remaining event handlers forward*/
            for (; i < (obj->spec_attr->event_dsc_cnt - 1); i++)
            {
                obj->spec_attr->event_dsc[i] = obj->spec_attr->event_dsc[i + 1];
            }

            obj->spec_attr->event_dsc_cnt--;
            obj->spec_attr->event_dsc = lv_mem_realloc(obj->spec_attr->event_dsc,
                                        obj->spec_attr->event_dsc_cnt * sizeof(lv_event_dsc_t));
            LV_ASSERT_MALLOC(obj->spec_attr->event_dsc);
            return true;
        }
    }

    /*No event handler found*/
    return false;
}

bool lv_obj_remove_event_cb_with_user_data(lv_obj_t* obj,
        lv_event_cb_t event_cb, const void* user_data)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    if (obj->spec_attr == NULL)
    {
        return false;
    }

    int32_t i = 0;

    for (i = 0; i < obj->spec_attr->event_dsc_cnt; i++)
    {
        if ((event_cb == NULL || obj->spec_attr->event_dsc[i].cb == event_cb) &&
                obj->spec_attr->event_dsc[i].user_data == user_data)
        {
            /*Shift the remaining event handlers forward*/
            for (; i < (obj->spec_attr->event_dsc_cnt - 1); i++)
            {
                obj->spec_attr->event_dsc[i] = obj->spec_attr->event_dsc[i + 1];
            }

            obj->spec_attr->event_dsc_cnt--;
            obj->spec_attr->event_dsc = lv_mem_realloc(obj->spec_attr->event_dsc,
                                        obj->spec_attr->event_dsc_cnt * sizeof(lv_event_dsc_t));
            LV_ASSERT_MALLOC(obj->spec_attr->event_dsc);
            return true;
        }
    }

    /*No event handler found*/
    return false;
}

bool lv_obj_remove_event_dsc(lv_obj_t* obj, struct _lv_event_dsc_t* event_dsc)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    if (obj->spec_attr == NULL)
    {
        return false;
    }

    int32_t i = 0;

    for (i = 0; i < obj->spec_attr->event_dsc_cnt; i++)
    {
        if (&obj->spec_attr->event_dsc[i] == event_dsc)
        {
            /*Shift the remaining event handlers forward*/
            for (; i < (obj->spec_attr->event_dsc_cnt - 1); i++)
            {
                obj->spec_attr->event_dsc[i] = obj->spec_attr->event_dsc[i + 1];
            }

            obj->spec_attr->event_dsc_cnt--;
            obj->spec_attr->event_dsc = lv_mem_realloc(obj->spec_attr->event_dsc,
                                        obj->spec_attr->event_dsc_cnt * sizeof(lv_event_dsc_t));
            LV_ASSERT_MALLOC(obj->spec_attr->event_dsc);
            return true;
        }
    }

    /*No event handler found*/
    return false;
}

void* lv_obj_get_event_user_data(struct _lv_obj_t* obj, lv_event_cb_t event_cb)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    if (obj->spec_attr == NULL)
    {
        return NULL;
    }

    int32_t i = 0;

    for (i = 0; i < obj->spec_attr->event_dsc_cnt; i++)
    {
        if (event_cb == obj->spec_attr->event_dsc[i].cb)
        {
            return obj->spec_attr->event_dsc[i].user_data;
        }
    }

    return NULL;
}

lv_indev_t* lv_event_get_indev(lv_event_t* e)
{

    if (e->code == LV_EVENT_PRESSED ||
            e->code == LV_EVENT_PRESSING ||
            e->code == LV_EVENT_PRESS_LOST ||
            e->code == LV_EVENT_SHORT_CLICKED ||
            e->code == LV_EVENT_LONG_PRESSED ||
            e->code == LV_EVENT_LONG_PRESSED_REPEAT ||
            e->code == LV_EVENT_CLICKED ||
            e->code == LV_EVENT_RELEASED ||
            e->code == LV_EVENT_SCROLL_BEGIN ||
            e->code == LV_EVENT_SCROLL_END ||
            e->code == LV_EVENT_SCROLL ||
            e->code == LV_EVENT_GESTURE ||
            e->code == LV_EVENT_KEY ||
            e->code == LV_EVENT_FOCUSED ||
            e->code == LV_EVENT_DEFOCUSED ||
            e->code == LV_EVENT_LEAVE)
    {
        return lv_event_get_param(e);
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return NULL;
    }
}

lv_obj_draw_part_dsc_t* lv_event_get_draw_part_dsc(lv_event_t* e)
{
    if (e->code == LV_EVENT_DRAW_PART_BEGIN ||
            e->code == LV_EVENT_DRAW_PART_END)
    {
        return lv_event_get_param(e);
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return NULL;
    }
}

lv_draw_ctx_t* lv_event_get_draw_ctx(lv_event_t* e)
{
    if (e->code == LV_EVENT_DRAW_MAIN ||
            e->code == LV_EVENT_DRAW_MAIN_BEGIN ||
            e->code == LV_EVENT_DRAW_MAIN_END ||
            e->code == LV_EVENT_DRAW_POST ||
            e->code == LV_EVENT_DRAW_POST_BEGIN ||
            e->code == LV_EVENT_DRAW_POST_END)
    {
        return lv_event_get_param(e);
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return NULL;
    }
}

const lv_area_t* lv_event_get_old_size(lv_event_t* e)
{
    if (e->code == LV_EVENT_SIZE_CHANGED)
    {
        return lv_event_get_param(e);
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return NULL;
    }
}

uint32_t lv_event_get_key(lv_event_t* e)
{
    if (e->code == LV_EVENT_KEY)
    {
        uint32_t* k = lv_event_get_param(e);

        if (k)
        {
            return *k;
        }
        else
        {
            return 0;
        }
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return 0;
    }
}

lv_anim_t* lv_event_get_scroll_anim(lv_event_t* e)
{
    if (e->code == LV_EVENT_SCROLL_BEGIN)
    {
        return lv_event_get_param(e);
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return 0;
    }
}

void lv_event_set_ext_draw_size(lv_event_t* e, lv_coord_t size)
{
    if (e->code == LV_EVENT_REFR_EXT_DRAW_SIZE)
    {
        lv_coord_t* cur_size = lv_event_get_param(e);
        *cur_size = LV_MAX(*cur_size, size);
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
    }
}

lv_point_t* lv_event_get_self_size_info(lv_event_t* e)
{
    if (e->code == LV_EVENT_GET_SELF_SIZE)
    {
        return lv_event_get_param(e);
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return 0;
    }
}

lv_hit_test_info_t* lv_event_get_hit_test_info(lv_event_t* e)
{
    if (e->code == LV_EVENT_HIT_TEST)
    {
        return lv_event_get_param(e);
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return 0;
    }
}

const lv_area_t* lv_event_get_cover_area(lv_event_t* e)
{
    if (e->code == LV_EVENT_COVER_CHECK)
    {
        lv_cover_check_info_t* p = lv_event_get_param(e);
        return p->area;
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
        return NULL;
    }
}

void lv_event_set_cover_res(lv_event_t* e, lv_cover_res_t res)
{
    if (e->code == LV_EVENT_COVER_CHECK)
    {
        lv_cover_check_info_t* p = lv_event_get_param(e);

        if (res > p->res)
        {
            p->res = res;    /*Save only "stronger" results*/
        }
    }
    else
    {
        LV_LOG_WARN("Not interpreted with this event code");
    }
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static lv_event_dsc_t* lv_obj_get_event_dsc(const lv_obj_t* obj, uint32_t id)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    if (!obj->spec_attr)
    {
        return NULL;
    }

    if (id >= obj->spec_attr->event_dsc_cnt)
    {
        return NULL;
    }

    return &obj->spec_attr->event_dsc[id];
}

static lv_res_t event_send_core(lv_event_t* e)
{
    EVENT_TRACE("Sending event %d to %p with %p param", e->code,
                (void*)e->current_target, e->param);

    /*Call the input device's feedback callback if set*/
    lv_indev_t* indev_act = lv_indev_get_act();

    if (indev_act)
    {
        if (indev_act->driver->feedback_cb)
        {
            indev_act->driver->feedback_cb(indev_act->driver, e->code);
        }

        if (e->stop_processing)
        {
            return LV_RES_OK;
        }

        if (e->deleted)
        {
            return LV_RES_INV;
        }
    }

    lv_res_t res = LV_RES_OK;
    lv_event_dsc_t* event_dsc = lv_obj_get_event_dsc(e->current_target, 0);

    uint32_t i = 0;

    while (event_dsc && res == LV_RES_OK)
    {
        if (event_dsc->cb
                && ((event_dsc->filter & LV_EVENT_PREPROCESS) == LV_EVENT_PREPROCESS)
                && (event_dsc->filter == (LV_EVENT_ALL | LV_EVENT_PREPROCESS) ||
                    (event_dsc->filter & ~LV_EVENT_PREPROCESS) == e->code))
        {
            e->user_data = event_dsc->user_data;
            event_dsc->cb(e);

            if (e->stop_processing)
            {
                return LV_RES_OK;
            }

            /*Stop if the object is deleted*/
            if (e->deleted)
            {
                return LV_RES_INV;
            }
        }

        i++;
        event_dsc = lv_obj_get_event_dsc(e->current_target, i);
    }

    res = lv_obj_event_base(NULL, e);

    event_dsc = res == LV_RES_INV ? NULL : lv_obj_get_event_dsc(e->current_target,
                0);

    i = 0;

    while (event_dsc && res == LV_RES_OK)
    {
        if (event_dsc->cb && ((event_dsc->filter & LV_EVENT_PREPROCESS) == 0)
                && (event_dsc->filter == LV_EVENT_ALL || event_dsc->filter == e->code))
        {
            e->user_data = event_dsc->user_data;
            event_dsc->cb(e);

            if (e->stop_processing)
            {
                return LV_RES_OK;
            }

            /*Stop if the object is deleted*/
            if (e->deleted)
            {
                return LV_RES_INV;
            }
        }

        i++;
        event_dsc = lv_obj_get_event_dsc(e->current_target, i);
    }

    if (res == LV_RES_OK && e->current_target->parent && event_is_bubbled(e))
    {
        e->current_target = e->current_target->parent;
        res = event_send_core(e);

        if (res != LV_RES_OK)
        {
            return LV_RES_INV;
        }
    }

    return res;
}

static bool event_is_bubbled(lv_event_t* e)
{
    if (e->stop_bubbling)
    {
        return false;
    }

    /*Event codes that always bubble*/
    switch (e->code)
    {
    case LV_EVENT_CHILD_CREATED:
    case LV_EVENT_CHILD_DELETED:
        return true;

    default:
        break;
    }

    /*Check other codes only if bubbling is enabled*/
    if (lv_obj_has_flag(e->current_target, LV_OBJ_FLAG_EVENT_BUBBLE) == false)
    {
        return false;
    }

    switch (e->code)
    {
    case LV_EVENT_HIT_TEST:
    case LV_EVENT_COVER_CHECK:
    case LV_EVENT_REFR_EXT_DRAW_SIZE:
    case LV_EVENT_DRAW_MAIN_BEGIN:
    case LV_EVENT_DRAW_MAIN:
    case LV_EVENT_DRAW_MAIN_END:
    case LV_EVENT_DRAW_POST_BEGIN:
    case LV_EVENT_DRAW_POST:
    case LV_EVENT_DRAW_POST_END:
    case LV_EVENT_DRAW_PART_BEGIN:
    case LV_EVENT_DRAW_PART_END:
    case LV_EVENT_REFRESH:
    case LV_EVENT_DELETE:
    case LV_EVENT_CHILD_CREATED:
    case LV_EVENT_CHILD_DELETED:
    case LV_EVENT_CHILD_CHANGED:
    case LV_EVENT_SIZE_CHANGED:
    case LV_EVENT_STYLE_CHANGED:
    case LV_EVENT_GET_SELF_SIZE:
        return false;

    default:
        return true;
    }
}
